package com.ayz.gateway.center.infrastructure.repository;


import com.ayz.gateway.center.domain.model.ApplicationData;
import com.ayz.gateway.center.domain.model.ApplicationLiveInfo;
import com.ayz.gateway.center.domain.repository.IApiRepository;
import com.ayz.gateway.center.infrastructure.notify.ApiDataChangeEvent;
import com.ayz.gateway.common.constants.ApiData;
import com.ayz.gateway.common.constants.ApplicationConfigWrapper;
import com.ayz.gateway.common.constants.DataVersion;
import com.ayz.gateway.common.notify.NotifyCenter;
import lombok.Data;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @ClassName LocalApiRepository
 * @Description TODO: 网关注册中心的API存储中心
 * @Author 快乐的星球
 * @Date 2023/5/13 15:51
 * @Version 1.0
 **/
@Data
@Component
public class LocalApiRepository implements IApiRepository {
    private static final Logger LOG= LoggerFactory.getLogger(LocalApiRepository.class);

    /**
     * 微服务的名称 和 ApiData的映射map
     */
    private final HashMap<String,List<ApiData>> applicationApiMap=new HashMap<>(1024);
    /**
     * 微服务的名称 和 ApplicationData的映射map
     */
    private final HashMap<String, ApplicationData> applicationDataHashMap=new HashMap<>(1024);
    /**
     * 微服务网关客户端SDK的地址 和 ApplicationLiveInfo的 map映射关系
     */
    private final HashMap<String, ApplicationLiveInfo> applicationLiveInfoMap=new HashMap<>(1024);
    /**
     * 网关组 和 该组下所有应用程序名称 applicationName的映射关系
     */
    private final HashMap<String,HashSet<String>> gatewayGroupInfoMap=new HashMap<>(128);
    /**
     * 读写锁
     */
    private final ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();

    @Override
    public void addApiData(ApiData apiData) {
        List<ApiData> apiDataList = applicationApiMap.get(apiData.getApplicationName());
        if(apiDataList==null){
            apiDataList=new ArrayList<>();
        }
        apiDataList.add(apiData);
    }
    /**
     * 导出当前网关注册中心中所有 应用程序所提供的服务列表
     * @return
     */
    @Override
    public List<ApiData> getApiDataList() {
        List<ApiData> result=new ArrayList<>();
        ReentrantReadWriteLock.ReadLock readLock = readWriteLock.readLock();
        readLock.lock();
        try {
            applicationApiMap.forEach((applicationName,apiDataList)->{
                if(apiDataList!=null && !apiDataList.isEmpty()){
                    result.addAll(apiDataList);
                }
            });
            return result;
        }finally {
            readLock.unlock();
        }
    }
    /**
     * 向网关注册中心中注册一个应用程序API
     * 主要是维护四个Map结构
     */
    public void registerApplication(String address,
                                    String applicationName,
                                    ApplicationConfigWrapper configWrapper,
                                    String gatewayGroup){
        try {
            if(configWrapper==null){
                /**
                 * 该应用程序不提供任何服务 注册无意义
                 */
                return;
            }
            LOG.info("应用程序API注册!address:{},applicationName:{},gatewayGroup:{}",address,applicationName,gatewayGroup);
            ReentrantReadWriteLock.WriteLock writeLock = readWriteLock.writeLock();
            writeLock.lock();
            try {
                //1:维护:applicationDataHashMap
                ApplicationData applicationData = this.applicationDataHashMap.get(applicationName);
                boolean isFirstRegistry=false;
                if(applicationData==null){
                    isFirstRegistry=true;
                    HashSet<String> applicationAddressSet=new HashSet<>();
                    applicationAddressSet.add(address);
                    applicationData=new ApplicationData(applicationName,applicationAddressSet,gatewayGroup);
                    this.applicationDataHashMap.put(applicationName,applicationData);
                }else {
                    applicationData.getApplicationAddressSet().add(address);
                }
                //2: 维护applicationApiMap的信息
                if(isFirstRegistry || isApplicationConfigChange(address,configWrapper.getDataVersion())){
                    //发布ApiDataChangeEvent
                    ApiDataChangeEvent apiDataChangeEvent=new ApiDataChangeEvent(gatewayGroup);
                    NotifyCenter.getINSTANCE().publishApplicationEvent(apiDataChangeEvent);
                    this.applicationApiMap.put(applicationName,configWrapper.getApiDataList());
                }
                //3: 维护applicationLiveInfoMap的信息
                ApplicationLiveInfo applicationLiveInfo = this.applicationLiveInfoMap.get(address);
                if(applicationLiveInfo==null){
                    //首次注册
                    applicationLiveInfo=new ApplicationLiveInfo();
                    applicationLiveInfo.setApplicationName(applicationName);
                    applicationLiveInfo.setGatewayGroup(gatewayGroup);
                }
                applicationLiveInfo.setLastHeartBeatTimestamp(System.currentTimeMillis());
                applicationLiveInfo.setDataVersion(configWrapper.getDataVersion());
                this.applicationLiveInfoMap.put(address,applicationLiveInfo);

                //4: 维护gatewayGroupInfoMap的信息
                HashSet<String> applicationNameSet = this.gatewayGroupInfoMap.get(gatewayGroup);
                if(applicationNameSet==null || applicationNameSet.isEmpty()){
                    applicationNameSet=new HashSet<>();
                    this.gatewayGroupInfoMap.put(gatewayGroup,applicationNameSet);
                }
                applicationNameSet.add(applicationName);
            }finally {
                if(writeLock.isHeldByCurrentThread()){
                    writeLock.unlock();
                }
            }
        }catch (Exception ex){
            LOG.error("registerApplication failed!",ex);
        }
    }
    public boolean isApplicationConfigChange(String address, DataVersion dataVersion){
        ApplicationLiveInfo applicationLiveInfo = this.applicationLiveInfoMap.get(address);
        if(applicationLiveInfo==null){
            return true;
        }else {
            DataVersion lastVersion = applicationLiveInfo.getDataVersion();
            return !lastVersion.equals(dataVersion);
        }
    }


    public void scanNotActiveApplication(long applicationChannelExpiredTime){
        try {
            List<String> unHealthAddressList=new ArrayList<>();
            //1:扫描那些不健康的节点 超过20S没有心跳
            this.applicationLiveInfoMap.forEach((address,applicationLiveInfo)->{
                long lastHeartBeatTimestamp = applicationLiveInfo.getLastHeartBeatTimestamp();
                long now=System.currentTimeMillis();
                if(now-lastHeartBeatTimestamp>=applicationChannelExpiredTime){
                    //断开连接
                    unHealthAddressList.add(address);
                    LOG.warn("应用程序心跳超时,进行服务API接口列表剔除!address:{}",address);
                }
            });
            //2:移除这些不健康节点注册的服务信息
            //2.1: 获取到读写锁
            ReentrantReadWriteLock.WriteLock writeLock = this.readWriteLock.writeLock();
            writeLock.lock();
            try {
                unHealthAddressList.forEach((address)->{
                    //1:维护applicationLiveInfoMap
                    ApplicationLiveInfo applicationLiveInfo = this.applicationLiveInfoMap.get(address);
                    if(applicationLiveInfo==null){
                        return;
                    }
                    String applicationName = applicationLiveInfo.getApplicationName();
                    String gatewayGroup = applicationLiveInfo.getGatewayGroup();
                    this.applicationLiveInfoMap.remove(address);
                    //2:如果当前节点是这个applicationName的最后一个节点，那么整个服务信息全都要剔除
                    boolean isLastApplication=false;
                    HashMap<String, ApplicationData> applicationDataHashMap = this.applicationDataHashMap;
                    ApplicationData applicationData = applicationDataHashMap.get(applicationName);
                    applicationData.getApplicationAddressSet().remove(address);
                    isLastApplication=applicationData.getApplicationAddressSet().isEmpty();

                    if(isLastApplication){
                        //3:维护applicationDataHashMap
                        applicationDataHashMap.remove(applicationName);
                        //4:维护applicationApiMap
                        this.applicationApiMap.remove(applicationName);
                        //5:维护gatewayGroupInfoMap
                        HashMap<String, HashSet<String>> gatewayGroupInfoMap = this.getGatewayGroupInfoMap();
                        HashSet<String> addressSet = this.getGatewayGroupInfoMap().get(gatewayGroup);
                        addressSet.remove(applicationName);
                        if(addressSet.isEmpty()){
                            this.getGatewayGroupInfoMap().remove(gatewayGroup);
                        }
                        //发布ApiDataChangeEvent
                        ApiDataChangeEvent apiDataChangeEvent=new ApiDataChangeEvent(gatewayGroup);
                        NotifyCenter.getINSTANCE().publishApplicationEvent(apiDataChangeEvent);
                    }
                });
            }finally {
                if(writeLock.isHeldByCurrentThread()){
                    writeLock.unlock();
                }
            }
        }catch (Exception ex){
            LOG.warn("服务端健康检查失败!");
        }
    }

    @Override
    public List<ApiData> getApiDataListByGroup(String gatewayGroup) {
        HashSet<String> applicationNameSet = getGatewayGroupInfoMap().get(gatewayGroup);
        //TODO: 获取读锁
        ReentrantReadWriteLock.ReadLock readLock = this.readWriteLock.readLock();
        readLock.lock();
        try {
            if(applicationNameSet==null || applicationNameSet.isEmpty()){
                return new ArrayList<>();
            }
            //当前网关组下存在应用程序
            List<ApiData> apiDataList=new ArrayList<>();
            applicationNameSet.forEach((applicationName)->{
                List<ApiData> cur = applicationApiMap.get(applicationName);
                if(cur!=null && !cur.isEmpty()){
                    apiDataList.addAll(cur);
                }
            });
            return apiDataList;
        }finally {
            readLock.unlock();
        }
    }
}
